package cn.tworice.backup.service;

import cn.tworice.backup.config.BackupProperties;
import cn.tworice.backup.executor.BackupExecutor;
import cn.tworice.backup.executor.DatabaseEnum;
import cn.tworice.backup.executor.DatabaseParam;
import cn.tworice.backup.scheduler.BackupScheduledTask;
import cn.tworice.common.framework.mail.bo.Attachment;
import cn.tworice.common.framework.mail.bo.SendMailBO;
import cn.tworice.common.framework.mail.core.MailExecutor;
import cn.tworice.common.util.StringUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.jdbc.DataSourceProperties;
import org.springframework.boot.autoconfigure.mongo.MongoProperties;
import org.springframework.context.annotation.Lazy;
import org.springframework.scheduling.Trigger;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.io.File;
import java.util.Collections;

/**
 * 备份服务
 */
@Service
@Slf4j
public class BackupService {

    @Resource
    private BackupProperties backupProperties;

    @Lazy
    @Resource
    private MongoProperties mongoProperties;

    @Resource
    private MailExecutor mailExecutor;

    @Resource
    private DataSourceProperties dataSourceProperties;

    @Resource
    private BackupScheduledTask backupScheduledTask;

    /**
     * 初始化计划任务
     */
    @PostConstruct
    public void init() {
        if(!backupProperties.getEnable()){
            return;
        }
        DatabaseParam param = buildDatabaseParam();
        log.info("初始化数据库备份计划：{},周期：{}", param, backupProperties.getCron());
        this.scheduleTaskAtSpecificTime(param, backupProperties.getCron());
    }

    /**
     * 定时任务
     *
     * @param param  数据库参数
     * @param hour   小时
     * @param minute 分钟
     */
    public void scheduleTaskAtSpecificTime(DatabaseParam param, int hour, int minute) {
        Trigger trigger = new CronTrigger(String.format("0 %d %d * * *", minute, hour));
        backupScheduledTask.scheduleTask(param, trigger);
    }

    /**
     * 定时任务
     *
     * @param param 数据库参数
     * @param cron  cron表达式
     */
    public void scheduleTaskAtSpecificTime(DatabaseParam param,String cron) {
        Trigger trigger = new CronTrigger(cron);
        backupScheduledTask.scheduleTask(param, trigger);
    }

    /**
     * 备份
     * @param param 数据库参数
     */
    public void backup(DatabaseParam param) {
        BackupExecutor executor= this.getBackupExecutorByType(param);
        String path = System.getProperty("user.dir") + File.separator + backupProperties.getPath();
        StringBuilder builder = new StringBuilder(path);
        executor.backup(builder);
        log.info("数据库备份执行成功：{}",builder);
        this.sendMail(builder.toString(),param);
    }

    /**
     * 获取备份执行器
     * @param param 数据库参数
     * @return 备份执行器
     */
    public BackupExecutor getBackupExecutorByType(DatabaseParam param) {
        DatabaseEnum executorByType = DatabaseEnum.getExecutorByType(param.getType());
        if (executorByType == null) {
            throw new RuntimeException("未找到数据库类型");
        }
        try {
            return executorByType.getClazz().getConstructor(DatabaseParam.class).newInstance(param);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 测试备份
     */
    public void backupTest() {
        DatabaseParam param = buildDatabaseParam();
        backup(param);
    }

    /**
     * 发送邮件
     * @param path 本地存储的备份文件
     * @param param 数据库参数
     */
    private void sendMail(String path, DatabaseParam param) {
        if (StringUtils.isBlank(param.getEmail()) || StringUtils.isBlank(path)) {
            return;
        }
        SendMailBO sendMailBO = new SendMailBO();
        sendMailBO.setTo(param.getEmail());
        sendMailBO.setSubject("【数据库备份】" + param.getDatabaseName());
        sendMailBO.setSimple(false);
        sendMailBO.setContent("数据库备份");

        File file = new File(path);
        Attachment attachment = new Attachment();
        attachment.setName(file.getName());
        attachment.setFile(file);
        sendMailBO.setAttachment(Collections.singletonList(attachment));
        mailExecutor.sendMail(sendMailBO);
    }

    private String getDatabaseName(String url) {
        String[] urlParts = url.split("/");
        return urlParts[urlParts.length - 1].split("\\?")[0];
    }

    private String getType(String url) {
        String[] urlParts = url.split(":");
        return urlParts[1];
    }

    private DatabaseParam buildDatabaseParam() {
        DatabaseParam param = new DatabaseParam();
        switch (backupProperties.getDatabaseType()) {
            case "mongodb":
                param.setDatabaseName(mongoProperties.getDatabase());
                param.setType("mongodb");
                param.setUrl(this.buildMongoDBUri());
                param.setUsername(mongoProperties.getUsername());
                param.setPassword(String.copyValueOf(mongoProperties.getPassword()));
                break;
            default:
                param.setDatabaseName(this.getDatabaseName(dataSourceProperties.getUrl()));
                param.setType(this.getType(dataSourceProperties.getUrl()));
                param.setUrl(dataSourceProperties.getUrl());
                param.setDriver(dataSourceProperties.getDriverClassName());
                param.setUsername(dataSourceProperties.getUsername());
                param.setPassword(dataSourceProperties.getPassword());
        }
        param.setEmail(backupProperties.getEmail());

        return param;
    }

    private String buildMongoDBUri() {
        return String.format("mongodb://%s:%s@%s:%d/%s?authSource=%s",
                mongoProperties.getUsername(),
                String.copyValueOf(mongoProperties.getPassword()),
                mongoProperties.getHost(),
                mongoProperties.getPort(),
                mongoProperties.getDatabase(),
                mongoProperties.getAuthenticationDatabase() != null ? mongoProperties.getAuthenticationDatabase() : "admin"
        );
    }


}
